﻿using Microsoft.CodeAnalysis;
using System;
using System.Collections.Immutable;
using System.Text;

namespace DimensionsHelper.SourceGeneration
{

    public static class Extensions
    {

        public static string ToEscapedString(this string str)
        {
            var builder = new StringBuilder();

            foreach (var ch in str)
            {
                switch (ch)
                {
                    case '"':
                    case '\\':
                    case '\r':
                    case '\n':
                    case '\0':
                    case '\a':
                    case '\b':
                    case '\f':
                    case '\t':
                    case '\v':
                        builder.Append('\\');
                        break;

                    default:
                        break;
                }

                builder.Append(ch);
            }

            return builder.ToString();
        }


        public static string ToTypeShortName(this string sourceString)
        {
            if (sourceString == null)
            {
                return null;
            }

            int ltIndex = sourceString.IndexOf('<');

            if (ltIndex >= 0)
            {
                sourceString = sourceString.Substring(0, ltIndex);
            }

            var splitted = sourceString.Split('.');

            if (splitted.Length > 0)
            {
                return splitted[splitted.Length - 1];
            }

            return sourceString;
        }


        public static string ToTypeShortNameWithoutAttribute(this string sourceString)
        {
            if (string.IsNullOrEmpty(sourceString))
            {
                return string.Empty;
            }

            var shortName = sourceString.ToTypeShortName();

            if (shortName != "Attribute" && shortName.EndsWith("Attribute"))
            {
                shortName = shortName.Substring(0, shortName.Length - 9);
            }

            return shortName;
        }


        public static Location GetLocation(this ISymbol symbol)
        {
            return symbol.Locations.Length > 0 ? symbol.Locations[0] : null;
        }


        public static Location GetLocation(this AttributeData attributeData)
        {
            var reference = attributeData.ApplicationSyntaxReference;
            return reference?.SyntaxTree.GetLocation(reference.Span);
        }


        public static bool ContainsLocation(this Compilation compilation, Location location)
        {
            return location.SourceTree != null && compilation.ContainsSyntaxTree(location.SourceTree);
        }


        public static bool IsAssignableFrom(this ITypeSymbol baseType, ITypeSymbol type)
        {
            if (baseType == null || type == null)
            {
                return false;
            }

            if (baseType.TypeKind is TypeKind.Interface)
            {
                if (type.AllInterfaces.Contains((INamedTypeSymbol)baseType, SymbolEqualityComparer.Default))
                {
                    return true;
                }
            }

            var t = type as INamedTypeSymbol;
            while (t != null)
            {
                if (SymbolEqualityComparer.Default.Equals(baseType, t))
                {
                    return true;
                }

                t = t.BaseType;
            }

            return false;
        }



        // Copied from: https://github.com/dotnet/roslyn/blob/main/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SymbolVisibility.cs
        private enum SymbolVisibility
        {
            Public = 0,
            Internal = 1,
            Private = 2,
            Friend = Internal,
        }


        // copied from https://github.com/dotnet/roslyn/blob/main/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ISymbolExtensions.cs
        private static SymbolVisibility GetResultantVisibility(this ISymbol symbol)
        {
            // Start by assuming it's visible.
            SymbolVisibility visibility = SymbolVisibility.Public;

            switch (symbol.Kind)
            {
                case SymbolKind.Alias:
                    // Aliases are uber private.  They're only visible in the same file that they
                    // were declared in.
                    return SymbolVisibility.Private;

                case SymbolKind.Parameter:
                    // Parameters are only as visible as their containing symbol
                    return GetResultantVisibility(symbol.ContainingSymbol);

                case SymbolKind.TypeParameter:
                    // Type Parameters are private.
                    return SymbolVisibility.Private;
            }

            while (symbol != null && symbol.Kind != SymbolKind.Namespace)
            {
                switch (symbol.DeclaredAccessibility)
                {
                    // If we see anything private, then the symbol is private.
                    case Accessibility.NotApplicable:
                    case Accessibility.Private:
                        return SymbolVisibility.Private;

                    // If we see anything internal, then knock it down from public to
                    // internal.
                    case Accessibility.Internal:
                    case Accessibility.ProtectedAndInternal:
                        visibility = SymbolVisibility.Internal;
                        break;

                        // For anything else (Public, Protected, ProtectedOrInternal), the
                        // symbol stays at the level we've gotten so far.
                }

                symbol = symbol.ContainingSymbol;
            }

            return visibility;
        }

        // Copied from: https://github.com/dotnet/roslyn/blob/main/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/CompilationExtensions.cs
        /// <summary>
        /// Gets a type by its metadata name to use for code analysis within a <see cref="Compilation"/>. This method
        /// attempts to find the "best" symbol to use for code analysis, which is the symbol matching the first of the
        /// following rules.
        ///
        /// <list type="number">
        ///   <item><description>
        ///     If only one type with the given name is found within the compilation and its referenced assemblies, that
        ///     type is returned regardless of accessibility.
        ///   </description></item>
        ///   <item><description>
        ///     If the current <paramref name="compilation"/> defines the symbol, that symbol is returned.
        ///   </description></item>
        ///   <item><description>
        ///     If exactly one referenced assembly defines the symbol in a manner that makes it visible to the current
        ///     <paramref name="compilation"/>, that symbol is returned.
        ///   </description></item>
        ///   <item><description>
        ///     Otherwise, this method returns <see langword="null"/>.
        ///   </description></item>
        /// </list>
        /// </summary>
        /// <param name="compilation">The <see cref="Compilation"/> to consider for analysis.</param>
        /// <param name="fullyQualifiedMetadataName">The fully-qualified metadata type name to find.</param>
        /// <returns>The symbol to use for code analysis; otherwise, <see langword="null"/>.</returns>
        public static INamedTypeSymbol GetBestTypeByMetadataName(this Compilation compilation, string fullyQualifiedMetadataName)
        {
            // Try to get the unique type with this name, ignoring accessibility
            var type = compilation.GetTypeByMetadataName(fullyQualifiedMetadataName)
            // Otherwise, try to get the unique type with this name originally defined in 'compilation'
                ?? compilation.Assembly.GetTypeByMetadataName(fullyQualifiedMetadataName);

            // Otherwise, try to get the unique accessible type with this name from a reference
            if (type is null)
            {
                foreach (var module in compilation.Assembly.Modules)
                {
                    foreach (var referencedAssembly in module.ReferencedAssemblySymbols)
                    {
                        var currentType = referencedAssembly.GetTypeByMetadataName(fullyQualifiedMetadataName);
                        if (currentType is null)
                            continue;

                        switch (currentType.GetResultantVisibility())
                        {
                            case SymbolVisibility.Public:
                            case SymbolVisibility.Internal when referencedAssembly.GivesAccessTo(compilation.Assembly):
                                break;

                            default:
                                continue;
                        }

                        if (type is object)
                        {
                            // Multiple visible types with the same metadata name are present
                            return null;
                        }

                        type = currentType;
                    }
                }
            }

            return type;
        }



        /// <summary>
        /// 获取带有命名空间的符号全名，不包含可能出现的泛型参数。
        /// </summary>
        public static string GetSymbolFullName(this ISymbol symbol)
        {
            if (symbol == null)
            {
                return null;
            }

            var displayName = symbol.ToDisplayString();

            if (displayName.EndsWith("?"))
            {
                displayName = displayName.Substring(0, displayName.Length - 1);
            }

            if (KnownSymbolHelper.IsBasicValueTypeDisplayName(displayName))
            {
                return displayName;
            }

            return "global::" + displayName;
        }



        public static string GetSymbolDocumentationCommentXml(this ISymbol symbol)
        {
            var doc = symbol.GetDocumentationCommentXml();

            if (!string.IsNullOrEmpty(doc))
            {
                var lines = new StringBuilder();
                bool isDocStart = false;

                foreach (var line in doc.Split('\n'))
                {
                    var lineText = line.Trim();
                    if (lineText.EndsWith("\r"))
                    {
                        lineText = lineText.Substring(0, lineText.Length - 1);
                    }

                    if (lineText == "<summary>")
                    {
                        isDocStart = true;
                    }

                    if (isDocStart)
                    {
                        lines.Append("/// ").Append(lineText);
                    }

                    if (lineText == "</summary>")
                    {
                        break;
                    }

                    lines.AppendLine();
                }

                return lines.ToString();
            }

            return string.Empty;
        }



        public static bool TryGetAttributeWithFullyQualifiedMetadataName(this ISymbol symbol, string name, out AttributeData attributeData)
        {
            attributeData = null;

            foreach (var attribute in symbol.GetAttributes())
            {
                var displayName = name.StartsWith("global::") ? attribute.AttributeClass.GetSymbolFullName() : attribute.AttributeClass.ToDisplayString();
                if (displayName == name)
                {
                    attributeData = attribute;
                    return true;
                }
            }

            return false;
        }


        /// <summary>
        /// 如果指定符号是带有<see cref="System.FlagsAttribute"/>特性的枚举类型符号，返回<see langword="true"/>
        /// </summary>
        public static bool IsFlags(this ITypeSymbol symbol)
        {
            if (symbol != null && symbol.TypeKind == TypeKind.Enum)
            {
                foreach (var attrData in symbol.GetAttributes())
                {
                    if (attrData.AttributeClass.ToDisplayString() == "System.FlagsAttribute")
                    {
                        return true;
                    }
                }
            }

            return false;
        }


        /// <summary>
        /// 如果指定符号是枚举符号，返回<see langword="true"/>
        /// </summary>
        public static bool IsEnum(this ITypeSymbol symbol)
        {
            return symbol != null && symbol.TypeKind == TypeKind.Enum;
        }



        public static bool IsOneOfName(this ISymbol symbol, params string[] names)
        {
            return Array.IndexOf(names, symbol.ToDisplayString()) >= 0;
        }


        
        public static object GetConstructorValueAt(this AttributeData attributeData, int index)
        {
            if (index >= 0 && index < attributeData.ConstructorArguments.Length)
            {
                return attributeData.ConstructorArguments[index].Value;
            }

            return null;
        }


        public static object GetNamedArgumentValue(this AttributeData attributeData, string argName)
        {
            foreach (var named in attributeData.NamedArguments)
            {
                if (named.Key == argName)
                {
                    return named.Value.Value;
                }
            }

            return null;
        }


        /// <summary>
        /// 查找指定名称的成员符号，会向上查找基类型。
        /// </summary>
        public static ImmutableArray<ISymbol> FindMembers(this ITypeSymbol typeSymbol, string memberName)
        {
            var symbol = typeSymbol;

            while (symbol != null)
            {
                var members = symbol.GetMembers(memberName);

                if (!members.IsEmpty)
                {
                    return members;
                }

                symbol = symbol.BaseType;
            }

            return ImmutableArray<ISymbol>.Empty;
        }

    }
}